[SwiftUI] ZStackとCircleを組み合わせて円形のプログレスバーを作る
はじめに
こんにちは。CX事業本部の平屋です。
本記事では、以下のような「円形のプログレスバー」をSwiftUIで作る実装を紹介します。
検証環境
- macOS Monterey 12.6
- Xcode Version 13.4
ベースの作成
まずはビューを2つ作成します。
CircularProgressBar
: プログレスバーのビューContentView
: プログレスバーを使うビュー
// プログレスバーのビュー struct CircularProgressBar: View { @Binding var progress: CGFloat var body: some View { ZStack { } } } // プログレスバーを使うビュー struct ContentView: View { @State var progressValue: CGFloat = 0.3 var body: some View { VStack { CircularProgressBar(progress: $progressValue) .frame(width: 150.0, height: 150.0) .padding(32.0) Spacer() } } }
今回紹介するプログレスバーの実装では、以下の3つをCircularProgressBar
内のZStack
に重ねて実現します。
- 背景の円
- 進捗を示す円
- 進捗率のテキスト
背景の円の作成
まずは、CircularProgressBar.body
のZStack
に「背景の円」を追加します。
円を追加
ZStack
にCircle()
を追加すると、塗りつぶしの円が描画されます。
// プログレスバーのビュー struct CircularProgressBar: View { @Binding var progress: CGFloat var body: some View { ZStack { // 背景の円 Circle() } } }
スタイルの適用
円形の線を描画するように修正します。
ZStack { // 背景の円 Circle() // 円形の線を描画するように指定 .stroke(lineWidth: 24.0) .opacity(0.3) .foregroundColor(.blue) }
「背景の円」はこれで完成です。
進捗を示す円の作成
次に、「進捗を示す円」をZStack
の手前側に追加します。
円を追加
ZStack
にCircle()
を追加します。背景の円と異なり、stroke
モディファイアにStrokeStyle
を与えて線の端の形状などを指定しています。
ZStack { // 背景の円 // ... // 進捗を示す円 Circle() // 線の端の形状などを指定 .stroke(style: StrokeStyle(lineWidth: 24, lineCap: .round, lineJoin: .round)) .foregroundColor(.blue) }
この時点では2つの円が同じ形状になっていて「背景の円」が全て隠れている状態です。
進捗分だけ描画されるようにする
進捗分だけ描画されるように修正します。
ZStack { // 背景の円 // ... // 進捗を示す円 Circle() // 始点/終点を指定して円を描画する // 始点/終点には0.0-1.0の範囲に正規化した値を指定する .trim(from: 0.0, to: min(progress, 1.0)) // 線の端の形状などを指定 // ... }
現時点の描画結果は以下のようになります。(円のデフォルトの原点は「時計の3時の位置」のようです。)
円の原点を修正する
rotationEffect
モディファイアを使って円の原点を修正します。
ZStack { // 背景の円 // ... // 進捗を示す円 Circle() // ... .foregroundColor(.blue) // デフォルトの原点は時計の12時の位置ではないので回転させる .rotationEffect(Angle(degrees: 270.0)) }
期待する表示になりました。「進捗を示す円」はこれで完成です。
進捗率のテキストの作成
最後に、「進捗率のテキスト」をZStack
の手前側に追加します。
ZStack { // 背景の円 Circle() // ... // 進捗を示す円 Circle() // ... // 進捗率のテキスト Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0)) .font(.largeTitle) .bold() }
期待する表示になりました。これで完成です。
まとめ
完成版のコード全体は以下の通りです。
// プログレスバーのビュー struct CircularProgressBar: View { @Binding var progress: CGFloat var body: some View { ZStack { // 背景の円 Circle() // ボーダーラインを描画するように指定 .stroke(lineWidth: 24.0) .opacity(0.3) .foregroundColor(.blue) // 進捗を示す円 Circle() // 始点/終点を指定して円を描画する // 始点/終点には0.0-1.0の範囲に正規化した値を指定する .trim(from: 0.0, to: min(progress, 1.0)) // 線の端の形状などを指定 .stroke(style: StrokeStyle(lineWidth: 24, lineCap: .round, lineJoin: .round)) .foregroundColor(.blue) // デフォルトの原点は時計の12時の位置ではないので回転させる .rotationEffect(Angle(degrees: 270.0)) // 進捗率のテキスト Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0)) .font(.largeTitle) .bold() } } } // プログレスバーを使うビュー struct ContentView: View { @State var progressValue: CGFloat = 0.3 var body: some View { VStack { CircularProgressBar(progress: $progressValue) .frame(width: 150.0, height: 150.0) .padding(32.0) Spacer() } } }